package com.dhj.demo.business.common.utils.excel.easyexcel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.formula.functions.T;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author  denghuaijun
 * @date 2020年11月11 14点09分
 * @desc 针对于alibaba easyExcel2.1.x版本excel操作工具
 */
public class EasyExcelUtil {

        /**
         * 同步无模型读（默认读取sheet0,从第2行开始读）
         * @param filePath
         * @return
         */
        public static List<Map<Integer, String>> syncRead(String filePath){
        return EasyExcelFactory.read(filePath).sheet().doReadSync();
    }

        /**
         * 同步无模型读（默认表头占一行，从第2行开始读）
         * @param filePath
         * @param sheetNo sheet页号，从0开始
         * @return
         */
        public static List<Map<Integer, String>> syncRead(String filePath, Integer sheetNo){
        return EasyExcelFactory.read(filePath).sheet(sheetNo).doReadSync();
    }

        /**
         * 同步无模型读（指定sheet和表头占的行数）
         * @param inputStream
         * @param sheetNo sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         * @return List<Map<colNum, cellValue>>
         */
        public static List<Map<Integer, String>> syncRead(InputStream inputStream, Integer sheetNo, Integer headRowNum){
        return EasyExcelFactory.read(inputStream).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
    }

        /**
         * 同步无模型读（指定sheet和表头占的行数）
         * @param file
         * @param sheetNo sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         * @return List<Map<colNum, cellValue>>
         */
        public static List<Map<Integer, String>> syncRead(File file, Integer sheetNo, Integer headRowNum){
        return EasyExcelFactory.read(file).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
    }

        /**
         * 同步无模型读（指定sheet和表头占的行数）
         * @param filePath
         * @param sheetNo sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         * @return List<Map<colNum, cellValue>>
         */
        public static List<Map<Integer, String>> syncRead(String filePath, Integer sheetNo, Integer headRowNum){
        return EasyExcelFactory.read(filePath).sheet(sheetNo).headRowNumber(headRowNum).doReadSync();
    }

        /**
         * 同步按模型读（默认读取sheet0,从第2行开始读）
         * @param filePath
         * @param clazz 模型的类类型（excel数据会按该类型转换成对象）
         * @return
         */
        public static List<T> syncReadModel(String filePath, Class clazz){
        return EasyExcelFactory.read(filePath).sheet().head(clazz).doReadSync();
    }

        /**
         * 同步按模型读（默认表头占一行，从第2行开始读）
         * @param filePath
         * @param clazz 模型的类类型（excel数据会按该类型转换成对象）
         * @param sheetNo sheet页号，从0开始
         * @return
         */
        public static List<T> syncReadModel(String filePath, Class clazz, Integer sheetNo){
        return EasyExcelFactory.read(filePath).sheet(sheetNo).head(clazz).doReadSync();
    }

        /**
         * 同步按模型读（指定sheet和表头占的行数）
         * @param inputStream
         * @param clazz 模型的类类型（excel数据会按该类型转换成对象）
         * @param sheetNo sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         * @return
         */
        public static List<T> syncReadModel(InputStream inputStream, Class clazz, Integer sheetNo, Integer headRowNum){
        return EasyExcelFactory.read(inputStream).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
    }

        /**
         * 同步按模型读（指定sheet和表头占的行数）
         * @param file
         * @param clazz 模型的类类型（excel数据会按该类型转换成对象）
         * @param sheetNo sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         * @return
         */
        public static List<T> syncReadModel(File file, Class clazz, Integer sheetNo, Integer headRowNum){
        return EasyExcelFactory.read(file).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
    }

        /**
         * 同步按模型读（指定sheet和表头占的行数）
         * @param filePath
         * @param clazz 模型的类类型（excel数据会按该类型转换成对象）
         * @param sheetNo sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         * @return
         */
        public static List<T> syncReadModel(String filePath, Class clazz, Integer sheetNo, Integer headRowNum){
        return EasyExcelFactory.read(filePath).sheet(sheetNo).headRowNumber(headRowNum).head(clazz).doReadSync();
    }

        /**
         * 异步无模型读（默认读取sheet0,从第2行开始读）
         * @param excelListener 监听器，在监听器中可以处理行数据LinkedHashMap，表头数据，异常处理等
         * @param filePath 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         * @return
         */
        public static void asyncRead(String filePath, AnalysisEventListener<T> excelListener){
        EasyExcelFactory.read(filePath, excelListener).sheet().doRead();
    }

        /**
         * 异步无模型读（默认表头占一行，从第2行开始读）
         * @param filePath 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         * @param excelListener 监听器，在监听器中可以处理行数据LinkedHashMap，表头数据，异常处理等
         * @param sheetNo sheet页号，从0开始
         * @return
         */
        public static void asyncRead(String filePath, AnalysisEventListener<T> excelListener, Integer sheetNo){
        EasyExcelFactory.read(filePath, excelListener).sheet(sheetNo).doRead();
    }

        /**
         * 异步无模型读（指定sheet和表头占的行数）
         * @param inputStream
         * @param excelListener 监听器，在监听器中可以处理行数据LinkedHashMap，表头数据，异常处理等
         * @param sheetNo sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         * @return
         */
        public static void asyncRead(InputStream inputStream, AnalysisEventListener<T> excelListener, Integer sheetNo, Integer headRowNum){
        EasyExcelFactory.read(inputStream, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

        /**
         * 异步无模型读（指定sheet和表头占的行数）
         * @param file
         * @param excelListener 监听器，在监听器中可以处理行数据LinkedHashMap，表头数据，异常处理等
         * @param sheetNo sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         * @return
         */
        public static void asyncRead(File file, AnalysisEventListener<T> excelListener, Integer sheetNo, Integer headRowNum){
        EasyExcelFactory.read(file, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

        /**
         * 异步无模型读（指定sheet和表头占的行数）
         * @param filePath
         * @param excelListener 监听器，在监听器中可以处理行数据LinkedHashMap，表头数据，异常处理等
         * @param sheetNo sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         * @return
         */
        public static void asyncRead(String filePath, AnalysisEventListener<T> excelListener, Integer sheetNo, Integer headRowNum){
        EasyExcelFactory.read(filePath, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

        /**
         * 异步按模型读取（默认读取sheet0,从第2行开始读）
         * @param filePath
         * @param excelListener 监听器，在监听器中可以处理行数据LinkedHashMap，表头数据，异常处理等
         * @param clazz 模型的类类型（excel数据会按该类型转换成对象）
         */
        public static void asyncReadModel(String filePath, AnalysisEventListener<T> excelListener, Class clazz){
        EasyExcelFactory.read(filePath, clazz, excelListener).sheet().doRead();
    }

        /**
         * 异步按模型读取（默认表头占一行，从第2行开始读）
         * @param filePath
         * @param excelListener 监听器，在监听器中可以处理行数据LinkedHashMap，表头数据，异常处理等
         * @param clazz 模型的类类型（excel数据会按该类型转换成对象）
         * @param sheetNo  sheet页号，从0开始
         */
        public static void asyncReadModel(String filePath, AnalysisEventListener<T> excelListener, Class clazz, Integer sheetNo){
        EasyExcelFactory.read(filePath, clazz, excelListener).sheet(sheetNo).doRead();
    }

        /**
         * 异步按模型读取
         * @param inputStream
         * @param excelListener 监听器，在监听器中可以处理行数据LinkedHashMap，表头数据，异常处理等
         * @param clazz 模型的类类型（excel数据会按该类型转换成对象）
         * @param sheetNo  sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         */
        public static void asyncReadModel(InputStream inputStream, AnalysisEventListener<T> excelListener, Class clazz, Integer sheetNo, Integer headRowNum){
        EasyExcelFactory.read(inputStream, clazz, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

        /**
         * 异步按模型读取
         * @param file
         * @param excelListener 监听器，在监听器中可以处理行数据LinkedHashMap，表头数据，异常处理等
         * @param clazz 模型的类类型（excel数据会按该类型转换成对象）
         * @param sheetNo  sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         */
        public static void asyncReadModel(File file, AnalysisEventListener<T> excelListener, Class clazz, Integer sheetNo, Integer headRowNum){
        EasyExcelFactory.read(file, clazz, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

        /**
         * 异步按模型读取
         * @param filePath
         * @param excelListener 监听器，在监听器中可以处理行数据LinkedHashMap，表头数据，异常处理等
         * @param clazz 模型的类类型（excel数据会按该类型转换成对象）
         * @param sheetNo  sheet页号，从0开始
         * @param headRowNum 表头占的行数，从0开始（如果要连表头一起读出来则传0）
         */
        public static void asyncReadModel(String filePath, AnalysisEventListener<T> excelListener, Class clazz, Integer sheetNo, Integer headRowNum){
        EasyExcelFactory.read(filePath, clazz, excelListener).sheet(sheetNo).headRowNumber(headRowNum).doRead();
    }

        /**
         * 无模板写文件
         * @param filePath
         * @param head 表头数据
         * @param data 表内容数据
         */
        public static void write(String filePath, List<List<String>> head, List<List<Object>> data){
        EasyExcel.write(filePath).head(head).sheet().doWrite(data);
    }

        /**
         * 无模板写文件
         * @param filePath
         * @param head 表头数据
         * @param data 表内容数据
         * @param sheetNo sheet页号，从0开始
         * @param sheetName sheet名称
         */
        public static void write(String filePath, List<List<String>> head, List<List<Object>> data, Integer sheetNo, String sheetName){
        EasyExcel.write(filePath).head(head).sheet(sheetNo, sheetName).doWrite(data);
    }

        /**
         * 根据excel模板文件写入文件
         * @param filePath
         * @param templateFileName
         * @param headClazz
         * @param data
         */
        public static void writeTemplate(String filePath, String templateFileName, Class headClazz, List data){
        EasyExcel.write(filePath, headClazz).withTemplate(templateFileName).sheet().doWrite(data);
    }

        /**
         * 根据excel模板文件写入文件
         * @param filePath
         * @param templateFileName
         * @param data
         */
        public static void writeTemplate(String filePath, String templateFileName, List data){
        EasyExcel.write(filePath).withTemplate(templateFileName).sheet().doWrite(data);
    }

        /**
         * 按模板写文件
         * @param filePath
         * @param headClazz 表头模板
         * @param data 数据
         */
        public static void write(String filePath, Class headClazz, List data){
        EasyExcel.write(filePath, headClazz).sheet().doWrite(data);
    }

        /**
         * 按模板写文件
         * @param filePath
         * @param headClazz 表头模板
         * @param data 数据
         * @param sheetNo sheet页号，从0开始
         * @param sheetName sheet名称
         */
        public static void write(String filePath, Class headClazz, List data, Integer sheetNo, String sheetName){
        EasyExcel.write(filePath, headClazz).sheet(sheetNo, sheetName).doWrite(data);
    }

        /**
         * 按模板写文件
         * @param filePath
         * @param headClazz 表头模板
         * @param data 数据
         * @param writeHandler 自定义的处理器，比如设置table样式，设置超链接、单元格下拉框等等功能都可以通过这个实现（需要注册多个则自己通过链式去调用）
         * @param sheetNo sheet页号，从0开始
         * @param sheetName sheet名称
         */
        public static void write(String filePath, Class headClazz, List data, WriteHandler writeHandler, Integer sheetNo, String sheetName){
        EasyExcel.write(filePath, headClazz).registerWriteHandler(writeHandler).sheet(sheetNo, sheetName).doWrite(data);
    }

        /**
         * 按模板写文件（包含某些字段）
         * @param filePath
         * @param headClazz 表头模板
         * @param data 数据
         * @param includeCols 过滤包含的字段，根据字段名称过滤
         * @param sheetNo sheet页号，从0开始
         * @param sheetName sheet名称
         */
        public static void writeInclude(String filePath, Class headClazz, List data, Set<String> includeCols, Integer sheetNo, String sheetName){
        EasyExcel.write(filePath, headClazz).includeColumnFiledNames(includeCols).sheet(sheetNo, sheetName).doWrite(data);
    }

        /**
         * 按模板写文件（排除某些字段）
         * @param filePath
         * @param headClazz 表头模板
         * @param data 数据
         * @param excludeCols 过滤排除的字段，根据字段名称过滤
         * @param sheetNo sheet页号，从0开始
         * @param sheetName sheet名称
         */
        public static void writeExclude(String filePath, Class headClazz, List data, Set<String> excludeCols, Integer sheetNo, String sheetName){
        EasyExcel.write(filePath, headClazz).excludeColumnFiledNames(excludeCols).sheet(sheetNo, sheetName).doWrite(data);
    }

        /**
         * 多个sheet页的数据链式写入
         * ExcelUtil.writeWithSheets(outputStream)
         *                 .writeModel(ExcelModel.class, excelModelList, "sheetName1")
         *                 .write(headData, data,"sheetName2")
         *                 .finish();
         * @param outputStream
         * @return
         */
        public static EasyExcelWriterFactory writeWithSheets(OutputStream outputStream){
        EasyExcelWriterFactory excelWriter = new EasyExcelWriterFactory(outputStream);
        return excelWriter;
    }

        /**
         * 多个sheet页的数据链式写入
         * ExcelUtil.writeWithSheets(file)
         *                 .writeModel(ExcelModel.class, excelModelList, "sheetName1")
         *                 .write(headData, data,"sheetName2")
         *                 .finish();
         * @param file
         * @return
         */
        public static EasyExcelWriterFactory writeWithSheets(File file){
        EasyExcelWriterFactory excelWriter = new EasyExcelWriterFactory(file);
        return excelWriter;
    }

        /**
         * 多个sheet页的数据链式写入
         * ExcelUtil.writeWithSheets(filePath)
         *                 .writeModel(ExcelModel.class, excelModelList, "sheetName1")
         *                 .write(headData, data,"sheetName2")
         *                 .finish();
         * @param filePath
         * @return
         */
        public static EasyExcelWriterFactory writeWithSheets(String filePath){
        EasyExcelWriterFactory excelWriter = new EasyExcelWriterFactory(filePath);
        return excelWriter;
    }

        /**
         * 多个sheet页的数据链式写入（失败了会返回一个有部分数据的Excel）
         * ExcelUtil.writeWithSheets(response, exportFileName)
         *                 .writeModel(ExcelModel.class, excelModelList, "sheetName1")
         *                 .write(headData, data,"sheetName2")
         *                 .finish();
         * @param response
         * @param exportFileName 导出的文件名称
         * @return
         */
        public static EasyExcelWriterFactory writeWithSheetsWeb(HttpServletResponse response, String exportFileName) throws
        IOException {
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码
        String fileName = URLEncoder.encode(exportFileName, "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
        EasyExcelWriterFactory excelWriter = new EasyExcelWriterFactory(response.getOutputStream());
        return excelWriter;
    }
    }

    /**
     * 默认按模型读取的监听器
     * @param <T>
     */
    @Slf4j
    class DefaultExcelListener<T> extends AnalysisEventListener<T>
    {
        private final List<T> rows = new ArrayList();

        @Override
        public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
            log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
        }

        @Override
        public void invoke(T object, AnalysisContext context) {
            rows.add(object);
            // 实际数据量比较大时，rows里的数据可以存到一定量之后进行批量处理（比如存到数据库），
            // 然后清空列表，以防止内存占用过多造成OOM
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            log.info("read {} rows", rows.size());
        }

        /**
         * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
         * @param exception
         * @param context
         * @throws Exception
         */
        @Override
        public void onException(Exception exception, AnalysisContext context) {
            log.error("解析失败，但是继续解析下一行:{}", exception.getMessage());
            if (exception instanceof ExcelDataConvertException) {
                ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
                log.error("第{}行，第{}列解析异常，数据为:{}", excelDataConvertException.getRowIndex(),
                        excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
            }
        }

        public List<T> getRows() {
            return rows;
        }
    }

